package com.mic.yzzn.plc.core.ins.toolUtils;

import com.mic.yzzn.plc.common.ins.datastructure.PLCContactorNumber;
import com.mic.yzzn.plc.common.ins.datastructure.PLCDataRegisterNumber;
import com.mic.yzzn.plc.common.instructions.basic.PLCRequestInstruction;
import com.mic.yzzn.plc.common.instructions.request.PLCDataRegisterReadInstruction;
import com.mic.yzzn.plc.common.instructions.request.PLCMultipleContactorReadInstruction;
import com.mic.yzzn.plc.common.instructions.request.PLCSingleContactorReadInstruction;
import com.mic.yzzn.plc.common.toolUtils.PLCContactorCode;
import com.mic.yzzn.plc.common.toolUtils.PLCDataCode;
import com.mic.yzzn.plc.core.control.datastructure.COMStationConfig;
import com.mic.yzzn.plc.core.control.datastructure.PLCConfig;
import com.mic.yzzn.plc.core.control.datastructure.PLCStationConfig;
import com.mic.yzzn.plc.core.control.datastructure.TCPStationConfig;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.Serializable;
import java.util.*;

/**
 * 负责读取配置文件的类
 */
public class PLC_XMLParser implements Serializable {
    /**
     * 是否正在读取
     */
    private boolean reading;
    /**
     * 配置文件
     */
    private PLCConfig config = new PLCConfig();
    /**
     * 最大寄存器读取范围
     */
    public int maxDataRegisterRange = 20;
    /**
     * 最大触点读取范围
     */
    public int maxContactorRange = 8;

    /**
     * 采集器工作配置信息
     */
    HashMap<String, PLCStationConfig> stationConfigMap = new HashMap<String, PLCStationConfig>();

    public boolean isReading() {
        return reading;
    }

    public PLCConfig getConfig() {
        return config;
    }

    public HashMap<String, PLCStationConfig> getStationConfigMap() {
        return stationConfigMap;
    }

    /**
     * 从XML中解析指令
     *
     * @param uri XML文件的URI
     * @throws Exception 异常
     */
    public void parseXMLFileFromURI(String uri) throws Exception {
        // 设置状态
        reading = true;
        DocumentBuilderFactory dBuilderFactory = DocumentBuilderFactory.newInstance();

        try {
            // 读入XML
            DocumentBuilder dBuilder = dBuilderFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(uri);

            // 读取配置信息
            parseGlobalConfig(doc);

            overdueAllStationConfig();
            // 读取TCPStation
            parseTCPStation(doc);
            // 读取COMStation
            parseCOMStation(doc);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 取消状态
        reading = false;
    }

    /**
     * 移除字符串中所有的空格
     *
     * @param originalString
     * @return
     */
    private String removeBlank(String originalString) {
        return StringUtils.deleteWhitespace(originalString);
    }

    /**
     * 返回station节点的字符串(已经删除空格)
     *
     * @param stationElement
     * @return
     */
    private String stationString(Element stationElement) {
        StringBuffer stationString = new StringBuffer();

        NodeList childNodes = stationElement.getChildNodes();

        // 遍历Station子节点
        for (int i = 0; i < childNodes.getLength(); ++i) {
            Node stationChild = childNodes.item(i);

            if (stationChild.getNodeName().equals("DataSection")) {
                NodeList dataSectionChildNodes = stationChild.getChildNodes();

                stationString.append(removeBlank(((Element) stationChild).getAttribute("class")));

                for (int j = 0; j < dataSectionChildNodes.getLength(); ++j) {
                    Node dataSectionChild = dataSectionChildNodes.item(j);
                    stationString.append(removeBlank(dataSectionChild.getTextContent()));
                }
            } else if (stationChild.getNodeName().equals("ContactPoint")) {
                stationString.append(removeBlank(((Element) stationChild).getAttribute("class")));
                stationString.append(removeBlank(stationChild.getTextContent()));
            } else {
                stationString.append(removeBlank(stationChild.getTextContent()));
            }
        }

        return stationString.toString();
    }

    /**
     * 解析全局Config信息
     *
     * @param doc
     */
    private void parseGlobalConfig(Document doc) {
        // 读取配置信息
        NodeList timout = doc.getElementsByTagName("TimeOut");
        NodeList tryToReadPLCNum = doc.getElementsByTagName("TryToReadPLCNum");
        NodeList writeSleepTime = doc.getElementsByTagName("WriteSleepTime");
        NodeList stopSendNumLimit = doc.getElementsByTagName("StopSendNumLimit");

        config = new PLCConfig(
                Integer.parseInt(timout.item(0).getTextContent()),
                Integer.parseInt(tryToReadPLCNum.item(0).getTextContent()),
                Integer.parseInt(writeSleepTime.item(0).getTextContent()),
                Integer.parseInt(stopSendNumLimit.item(0).getTextContent())
        );
    }

    private void overdueAllStationConfig() {
        if (!stationConfigMap.isEmpty()) {
            Set<String> keySet = stationConfigMap.keySet();

            for (Iterator<String> i = keySet.iterator(); i.hasNext(); ) {
                stationConfigMap.get(i.next()).overdue = true;
            }
        }
    }

    /**
     * 解析COMStation节点
     *
     * @param doc
     */
    private void parseCOMStation(Document doc) {
        NodeList stations = doc.getElementsByTagName("COMStation");

        // 遍历所有Station节点
        for (int c = 0; c < stations.getLength(); ++c) {
            Element stationNode = (Element) stations.item(c);

            // 获取hash值
            String hash = DigestUtils.md5Hex(stationString(stationNode));

            // 初始化stationConfig
            NodeList baudRateNode = stationNode.getElementsByTagName("BaudRate");
            NodeList dataBitsNode = stationNode.getElementsByTagName("DataBits");
            NodeList stopBitNode = stationNode.getElementsByTagName("StopBit");
            NodeList parityNode = stationNode.getElementsByTagName("Parity");
            NodeList nameNode = stationNode.getElementsByTagName("Name");
            NodeList locNode = stationNode.getElementsByTagName("LocationNumber");

            PLCStationConfig stationConfig = new COMStationConfig(
                    Integer.parseInt(baudRateNode.item(0).getTextContent()),
                    Integer.parseInt(dataBitsNode.item(0).getTextContent()),
                    Integer.parseInt(stopBitNode.item(0).getTextContent()),
                    Integer.parseInt(parityNode.item(0).getTextContent()),
                    nameNode.item(0).getTextContent());
            stationConfig.setLocationNumber(locNode.item(0).getTextContent());
            stationConfig.XMLFieldHashCode = hash;
            // 装该Station中所有指令的数组
            ArrayList<PLCRequestInstruction> instructions = new ArrayList<PLCRequestInstruction>();
            // 站号字符串
            String locationNumber = locNode.item(0).getTextContent();

            if (isSameStation(stationConfig)) {
                continue;
            } else {
                parseStation(locationNumber, stationNode, stationConfig, instructions);
            }
        }
    }

    /**
     * 解析TCPStation节点
     *
     * @param doc
     */
    private void parseTCPStation(Document doc) {
        NodeList stations = doc.getElementsByTagName("TCPStation");

        // 遍历所有Station节点
        for (int c = 0; c < stations.getLength(); ++c) {
            Element stationNode = (Element) stations.item(c);

            // 获取hash值
            String hash = DigestUtils.md5Hex(stationString(stationNode));

            // 初始化stationConfig
            NodeList hostNode = stationNode.getElementsByTagName("Host");
            NodeList portNode = stationNode.getElementsByTagName("Port");
            NodeList locNode = stationNode.getElementsByTagName("LocationNumber");

            PLCStationConfig stationConfig = new TCPStationConfig(hostNode.item(0).getTextContent(), Integer.parseInt(portNode.item(0).getTextContent()), locNode.item(0).getTextContent());
            stationConfig.XMLFieldHashCode = hash;
            // 装该Station中所有指令的数组
            ArrayList<PLCRequestInstruction> instructions = new ArrayList<PLCRequestInstruction>();
            // 站号字符串
            String locationNumber = locNode.item(0).getTextContent();

            if (isSameStation(stationConfig)) {
                continue;
            } else {
                parseStation(locationNumber, stationNode, stationConfig, instructions);
            }
        }
    }

    private void parseStation(String locationNumber, Element stationNode, PLCStationConfig stationConfig, ArrayList<PLCRequestInstruction> instructions) {

        // 获取stationNode的子节点
        NodeList childNodes = stationNode.getChildNodes();
        // 遍历Station所有子节点
        for (int i = 0; i < childNodes.getLength(); ++i) {
            Node stationChild = childNodes.item(i);

            String value = stationChild.getTextContent();

            if (stationChild.getNodeName().equals("CycleFlashTime")) {
                // 刷新时间
                stationConfig.setFlashTime(Integer.parseInt(value));
            }

            // 离散点配置
            if (stationChild.getNodeName().equals("ContactPoint")) {
                String classType = ((Element) stationChild).getAttribute("class");
                String address = ((Element) stationChild).getTextContent();

                if (classType.equals(PLCDataCode.PLC_DATA_REGISTER_CODE)) {
                    PLCDataRegisterReadInstruction dri = new PLCDataRegisterReadInstruction();
                    try {
                        dri.setLocationNumber(locationNumber);
                        dri.setDataRegisterNumber(new PLCDataRegisterNumber(address, address));
                        dri.setDataCode(classType);
                        dri.setBBC();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }

                    instructions.add(dri);
                } else if (classType.equals(PLCContactorCode.PLC_INNER_RELAY_CODE)) {
                    PLCSingleContactorReadInstruction sci = new PLCSingleContactorReadInstruction();
                    try {
                        sci.setLocationNumber(locationNumber);
                        sci.setContactorNumber(new PLCContactorNumber(address));
                        sci.setContactorCode(classType);
                        sci.setBBC();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }

                    instructions.add(sci);
                }
            }

            // 如果是dataSection节点则解析指令
            if (stationChild.getNodeName().equals("DataSection")) {
                NodeList dataSectionChildNodes = stationChild.getChildNodes();

                String classType = ((Element) stationChild).getAttribute("class");
                String beginAddress = "";
                String endAddress = "";

                for (int j = 0; j < dataSectionChildNodes.getLength(); ++j) {
                    Node dataSectionChild = dataSectionChildNodes.item(j);
                    // 取出起始地址
                    if (dataSectionChild.getNodeName().equals("BeginAddress")) {
                        beginAddress = dataSectionChild.getTextContent();
                    }
                    // 取出终止地址
                    if (dataSectionChild.getNodeName().equals("EndAddress")) {
                        endAddress = dataSectionChild.getTextContent();
                    }
                }

                // 解析指令并且添加到数组中
                if (classType.equals(PLCDataCode.PLC_DATA_REGISTER_CODE)) {
                    instructions.addAll(parseDataRegisterReadInstruction(locationNumber, beginAddress, endAddress));
                }
                if (classType.equals(PLCContactorCode.PLC_INNER_RELAY_CODE)) {
                    instructions.addAll(parseContactorReadInstruction(locationNumber, beginAddress, endAddress));
                }

            }
        }

        // station创建完毕,设置为新创建的station,放进Map中
        stationConfig.setInstructionList(instructions);
        stationConfig.updateTime = new Date();
        stationConfig.overdue = false;
        stationConfig.setLocationNumber(locationNumber);
        stationConfigMap.put(stationConfig.id(), stationConfig);
    }

    private boolean isSameStation(PLCStationConfig stationConfig) {
        // 尝试获取老的stationConfig
        PLCStationConfig oldStationConfig = stationConfigMap.get(stationConfig.id());

        if (oldStationConfig != null) // 若已经存在相同地址的配置
        {
            System.out.println("存在相同的config: " + oldStationConfig.id());

            if (oldStationConfig.XMLFieldHashCode.equals(stationConfig.XMLFieldHashCode)) // 相同的配置, 不更新
            {
                System.out.println(oldStationConfig.id() + " 配置相同, 不更新");
                oldStationConfig.overdue = false;
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * 解析寄存器读取指令
     *
     * @param locationNumber 站号
     * @param beginAddress   起始地址
     * @param endAddress     结束地址
     */
    private ArrayList<PLCDataRegisterReadInstruction> parseDataRegisterReadInstruction
    (String locationNumber, String beginAddress, String endAddress) {
        PLCDataRegisterNumber registerNumber = new PLCDataRegisterNumber(beginAddress, endAddress);
        ArrayList<PLCDataRegisterReadInstruction> returnArray = new ArrayList<PLCDataRegisterReadInstruction>();

        PLCDataRegisterReadInstruction readRegisterIns = new PLCDataRegisterReadInstruction();
        int locLength = locationNumber.length();
        // 设置站号
        readRegisterIns.setLocationNumber_high(locationNumber.substring(0, locLength / 2));
        readRegisterIns.setLocationNumber_low(locationNumber.substring(locLength / 2, locLength));
        // 设置寄存器代码
        readRegisterIns.setDataCode(PLCDataCode.PLC_DATA_REGISTER_CODE);

        // 如果寄存器编号之间的差值超出最大限制
        if (registerNumber.range() > this.maxDataRegisterRange) {
            int range = registerNumber.range();
            int beginNumber = Integer.parseInt(beginAddress);
            // 计算需要分隔的次数
            int time = range / this.maxDataRegisterRange;
            // 计算余下的地址数
            int overFlowNumber = range % this.maxDataRegisterRange;
            for (int i = 0; i < time; ++i) {
                // 初始化指令
                PLCDataRegisterReadInstruction insPiece = new PLCDataRegisterReadInstruction();
                // 设置站号
                insPiece.setLocationNumber_high(readRegisterIns.getLocationNumber_high());
                insPiece.setLocationNumber_low(readRegisterIns.getLocationNumber_low());
                // 设置寄存器代码
                insPiece.setDataCode(PLCDataCode.PLC_DATA_REGISTER_CODE);
                // 设置寄存器编号
                insPiece.setDataRegisterNumber(new PLCDataRegisterNumber(
                        String.valueOf(beginNumber),
                        String.valueOf(beginNumber + this.maxDataRegisterRange - 1)));
                // 设置检错码
                insPiece.setBBC();
                // 递增地址
                beginNumber += this.maxDataRegisterRange;
                // 添加指令
//				this.readRegisterInstructionList.add(insPiece);
                returnArray.add(insPiece);
            }

            // 如果有多余的地址
            if (overFlowNumber > 0) {
                // 设置寄存器编号
                readRegisterIns.setDataRegisterNumber(new PLCDataRegisterNumber(
                        String.valueOf(beginNumber),
                        endAddress));
                // 设置检错码
                readRegisterIns.setBBC();
                // 添加指令
//				this.readRegisterInstructionList.add(readRegisterIns);
                returnArray.add(readRegisterIns);
            }
        } else {
            // 设置寄存器编号
            readRegisterIns.setDataRegisterNumber(registerNumber);
            // 设置检错码
            readRegisterIns.setBBC();
            // 添加指令
//			this.readRegisterInstructionList.add(readRegisterIns);
            returnArray.add(readRegisterIns);
        }

        return returnArray;
    }

    /**
     * 解析多触点读取指令
     *
     * @param locationNumber 站号
     * @param beginAddress   起始地址
     * @param endAddress     结束地址
     */
    private ArrayList<PLCMultipleContactorReadInstruction> parseContactorReadInstruction
    (String locationNumber, String beginAddress, String endAddress) {
        ArrayList<PLCMultipleContactorReadInstruction> returnArray = new ArrayList<PLCMultipleContactorReadInstruction>();

        // 初始化触点编号
        PLCContactorNumber beginNumber = new PLCContactorNumber(beginAddress.substring(0, beginAddress.length() - 1),
                beginAddress.charAt(beginAddress.length() - 1));
        PLCContactorNumber endNumber = new PLCContactorNumber(endAddress.substring(0, endAddress.length() - 1),
                endAddress.charAt(endAddress.length() - 1));
        // 获取地址两个编号的差值
        int range = PLCContactorNumber.rangeOf(beginNumber, endNumber);

        // 初始化指令
        PLCMultipleContactorReadInstruction readIns = new PLCMultipleContactorReadInstruction();
        // 设置站号
        int locLength = locationNumber.length();
        readIns.setLocationNumber_high(locationNumber.substring(0, locLength / 2));
        readIns.setLocationNumber_low(locationNumber.substring(locLength / 2, locLength));
        // 初始化触点代码和触点编码数组
        ArrayList<String> insCodes = new ArrayList<String>();
        ArrayList<PLCContactorNumber> insNumbers = new ArrayList<PLCContactorNumber>();

        // 如果两个编号相差大于触点最大范围
        if (range > maxContactorRange) {
            // 计算要进行的循环次数
            int time = range / maxContactorRange;
            // 取得未能被整除的编号数量
            int overFlowNumber = range % maxContactorRange;

            for (int i = 0; i < time; ++i) {
                // 初始化指令
                PLCMultipleContactorReadInstruction insPiece = new PLCMultipleContactorReadInstruction();
                // 设置站号
                insPiece.setLocationNumber_high(locationNumber.substring(0, locLength / 2));
                insPiece.setLocationNumber_low(locationNumber.substring(locLength / 2, locLength));
                insPiece.setCount(maxContactorRange);

                // 初始化触点代码和触点编码数组
                ArrayList<String> cCodes = new ArrayList<String>();
                ArrayList<PLCContactorNumber> cNumbers = new ArrayList<PLCContactorNumber>();

                for (int j = 0; j < maxContactorRange; ++j) {
                    // 设置新的触点编号
                    PLCContactorNumber newNumber = new PLCContactorNumber(beginNumber.getBCDCode(), beginNumber.getHEXCode());
                    // 添加触点代码和触点编号
                    cCodes.add(PLCContactorCode.PLC_INNER_RELAY_CODE);
                    cNumbers.add(newNumber);
                    // 递增起始触点编号
                    if (!beginNumber.isMax()) {
                        beginNumber.add(1);
                    }
                }

                // 添加指令到数组中
                insPiece.setContactorCodes(cCodes);
                insPiece.setContactorNumbers(cNumbers);
                insPiece.setBBC();
//				this.readContactorInstructionList.add(insPiece);/
                returnArray.add(insPiece);
            }

            // 如果有多余的地址
            if (overFlowNumber > 0) {
                for (int i = 0; i < overFlowNumber; ++i) {
                    // 设置新的触点编号
                    PLCContactorNumber newNumber = new PLCContactorNumber(beginNumber.getBCDCode(), beginNumber.getHEXCode());
                    // 添加触点代码和触点编号
                    insCodes.add(PLCContactorCode.PLC_INNER_RELAY_CODE);
                    insNumbers.add(newNumber);
                    if (!beginNumber.isMax()) {
                        beginNumber.add(1);
                    }
                }

                // 添加最后一条指令到数组中
                readIns.setCount(overFlowNumber);
                readIns.setContactorCodes(insCodes);
                readIns.setContactorNumbers(insNumbers);
                readIns.setBBC();
//				this.readContactorInstructionList.add(readIns);
                returnArray.add(readIns);
            }

        } else {
            for (int i = 0; i < range; ++i) {
                // 设置新的触点编号
                PLCContactorNumber newNumber = new PLCContactorNumber(beginNumber.getBCDCode(), beginNumber.getHEXCode());
                // 添加触点代码和触点编号
                insCodes.add(PLCContactorCode.PLC_INNER_RELAY_CODE);
                insNumbers.add(newNumber);
                beginNumber.add(1);
            }

            // 添加指令到数组中
            readIns.setCount(range);
            readIns.setContactorCodes(insCodes);
            readIns.setContactorNumbers(insNumbers);
            readIns.setBBC();
//			this.readContactorInstructionList.add(readIns);
            returnArray.add(readIns);
        }

        return returnArray;
    }
}